package cn.tedu.d20_reflection;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;

/*
 本类用来测试反射
 */
public class Test1_Reflect {

    //public static void main(String[] args) { }

    /*单元测试方法：是java测试的最小单位，使用灵活
     * 语法要求：被@Test注解修饰+没有参数+public+无返回值类型(void) */

    //通过单元测试方法获取类的字节码对象(类对象)

    @Test
    public void getClazz() throws Exception{
        /*
        获取字节码对象三种方法
         */
        Class<?> student1 = Class.forName("com.tedu.d20_reflection.Student");
        Class<?> student2= Student.class;
        //先创建匿名对象，匿名对象没有名字，只使用一次，再获取字节码对象
        Class<?> student3 = new Student().getClass();

        System.out.println(student1);//反射得到类的字节码Class对象
        System.out.println(student2.getName());//获取类的全路径名
        System.out.println(student3.getSimpleName());//获取类名
        System.out.println(student3.getPackage().getName());//获取包名

        System.out.println("=============================");

    }

    //通过单元测试来获取构造方法
    @Test
    public void getConstructor(){
        //获取字节码Class对象
        Class<?> clazz = Student.class;

        //获取构造方法们，并放入cs数组中
        Constructor<?>[] cs = clazz.getConstructors();

        //获取每个构造方法，增强for(1 2:3){循环体} 3-遍历的数据 1-类型 2-变量名
        for (Constructor c: cs) {
            System.out.println(c.getName());//获取当前构造的名字
            System.out.println("=====================");
            Class[] cps = c.getParameterTypes();//获取参数类型们
            System.out.println(Arrays.toString(cps));
            System.out.println("----------------------");
        }

    }

    //通过单元测试来获取成员方法
    @Test
    public void getFunction() throws ClassNotFoundException {
        //1.获取类的字节码对象
        Class<?> clazz = Class.forName("com.tedu.d20_reflection.Student");

        //获取所有成员方法
        Method[] ms = clazz.getMethods();

        for (Method m:ms) {

            System.out.println(m.getName());

            Parameter[] mps = m.getParameters();
            System.out.println(Arrays.toString(mps));
        }

    }

    //通过单元测试获取成员变量
    @Test
    public void getFields(){
        //用方式三获取字节码对象
        Class<?> clazz = new Student().getClass();
        /*
         成员变量的修饰符必须是public才能获取到--
         */
        //获取所有的成员变量
        Field[] fs = clazz.getFields();

        //遍历数组，获取成员变量的信息
        for (Field f:fs) {
            System.out.println(f.getName());
            System.out.println("--------------");
            System.out.println(f.getType().getName());
        }

    }


     /*总结：利用反射技术创建对象的方式
     方式1：通过字节码对象直接调用newInstance(),触发的是无参构造来创建对象
     方式2：根据方法参数先获取指定的构造方法对象，再通过此对象调用newInstance()
     触发的是对应参数的构造函数来船舰对象，注意参数类型是字节码对象
     */
     //通过单元测试反射创建对象
    @Test
    public void createObject() throws Exception {
        //获取(字节码)类对象
        Class<?> clazz = Student.class;

        //方式一创建对象
        Object obj = clazz.newInstance();//触发无参构造
        System.out.println(obj);

        //获取指定的构造方法，根据方法的参数来指定
        //注意！！！传入的参数不是参数的直接类型，而是对应的字节码对象

        //方式二
        Constructor<?> c = clazz.getConstructor(String.class,int.class);
        //触发含参构造
        Object obj2 = c.newInstance("孙悟空",500);
        System.out.println(obj2);
        //利用刚刚拿到的构造方法对象，创建Student对象，触发全参构造

        /*为什么要把Object强转成子类类型Student？
        * 因为想使用子类的特有属性和方法！
        * 多态：向上转型：(花木兰替父从军) 为了同一调用方式，把所有子类
        * 类型都当作父类来看，比如异常
        * */
        Student s=(Student)obj2;//多态：向下转型
        System.out.println(s.name);
        System.out.println(s.age);
        s.tell();

    }



}
